﻿--[[****************************************************************
	DuckNet v1.09

	Author: Evil Duck
	****************************************************************
	Description:
        DuckNet is a support-addon for World of Warcraft addons to
        use when an addon needs to transmit and receive data.

	Credits:
        
		Thanks to wow.jaslaughter.com for the great BankItems addon
		that really helped this project get started.
		Additional thanks to:
			Yatlas and Gello
			wowwiki.com
			lua-users.org

	Official Site:
		http://duckiebank.googlecode.com
        
	****************************************************************]]

-- 1.09  Bugfixes, API-number: 2
-- 1.08  Added support for long lines and linebreaks
-- 1.06  Added support for instamp
-- 1.05  Slight bugfix in table copy, strengthened negotiation crash for modded DuckNets, better idle-checks, added DuckMod support
-- 1.04  Optionally includes version when polling, slight change of negotiation scheme, detection of multiple transmissions
-- 1.03  Changed to use the addon channels
-- 0.99  Mod from the original DuckieBank net-code


--[[***************************************************************************

		-=>            <=-
		-=>  User API  <=-
		-=>            <=-


		-=>  Concept  <=-

DuckNet have 4 main elements of it's information:
  1. Prefix
  2. Data names
  3. Data timestamp
  4. Callback functions

1. Prefix
	This is a texual representation of your addon. This can be any text, but
	don't make it too long. If you addon is called "MyAddonRocks", you could
	use "MyAR" or similar. This must be unique.

2. Data names
	Whenever your addon needs to send some data, it must give this data a name.
	F.ex if an addon sends some price-info, the data name could be "Price". This
	way an addon can have as many data blocks as the author wants, and the receiving
	addon will always know where the received data is determined to be stored.

3. Data timestamp
	Whenever an addon generates a block of data it should also make a note of
	the time. This is easily done with the "time()" function. The time for the
	generated data should then be supplied to DuckNet. There are two reasons why
	this is important:
		1. When an addon receives data, it can determine if it is to save it or
		   not. If it is older than what it already have, it should obviously not
		   save it.
		2. When an addon requests data, DuckNet will be able to determine which
		   addon has the newest data.

4. Callback funtions
	Callback functions are functions defined in your addon that is called by
	DuckNet when certain events happens. There is one function for each event,
	and DuckNet must be told what these functions are.
	The events are:
		1. New data has been received
			This function will receive the table as it was transmitted by the sender.
		2. Your data has been transmitted
			Tells your addon if data was transmitted or if transmission was aborted.
		3. INFO has been spotted on the network
		4. Request for a data timestamp
		5. Negotiation won/lost
		6. Communication timestamp


		-=>  Minimum usage of the API  <=-

1. Tell DuckNet your addon's details after both has been loaded.

Call function "DuckNet_Register" with your addon's information:
	DuckNet_Register()
		This function is explained below

2. Send data
	This is the absolute basic form of sending data. Call this function with
	the table containing the data you want to transmit:
		DuckNet_SendTable(prefix,table,nil,time())

	If your addon has more than one different blocks of data (tables) to
	transmit, this function can also use data names:
		DuckNet_SendTable(prefix,table,"Table_X",Table_X.TimeStamp)

	When the data has been transmitted, your TX callback-function will be
	called by DuckNet.

3. Request data
	In events like when a character have just logged in th WoW, your addon
	may want to request new data. This can be done with this function:
		DuckNet_SyncTable(prefix)

	If your addon has more than one block of data (tables), this function
	must also use data names:
		DuckNet_SyncTable(prefix,"Table_X",Table_X.TimeStamp)

	When the new data has been received, your RX callback-function will be
	called. If there is no newer data for your addon, no data will be
	transmitted by the other addons, so the RX callback-function will not be
	called either.


Hooking on to the addon
�����������������������
	The first you need to do is to tell DuckNet about your addon. You do this
	by calling the "Register" function.
		DuckNet_Register(prefix,ctype,cbRX,cbTX,cbINFO,cbCS,cbNW,cbIS)
			prefix	- This must be a textual prefix unique to your addon.
					  As an example, the DuckieBank addon uses "DuBa". The
					  prefix can be any length but short prefixes are
					  recommended, as WoW will kick a user from the server when
					  more than 254 characters are attempted sent in one single
					  line.
			ctype	- Addon channel-type to use for the registered prefix
					  ("GUILD", "PARTY", "RAID", "BATTLEGROUND")
			cbRX	- Callback: This function will be called when a reception
					  has finished.
							function cbRX(table)
							table - Pointer to the DuckNet-generated table
									containing the received information.
			cbTX	- Callback: This function will be called when a
					  transmission is finished.
							function cbTX(lines)
							lines - Number of lines transmitted minus 1.
			cbINFO	- Callback: When addon-specific information is received,
					  this function will be called with the table-pointer as
					  an argument.
							function cbINFO(table)
							table - Pointer to the DuckNet-generated table with
									addon-specific information.
			cbCS	- Callback: This function will be called when DuckNet need
					  to retrieve a data-stamp for a certain block of data.
							function cbCS(marker)
							marker - This marker is supplied by the
									 transmitting addon. This is to distinguish
									 between blocks of data with different
									 stamps. If your addon only have a single
									 block of data that it transmits, you can
									 omit the marker, and the argument given
									 will be nil.
							The return value must be numeric. If your addon
							does not use stamps, this value can be zero.
			cbNW	- Callback: This function will be called when a network
					  negotiation has finished.
							function cbNW(winner)
							winner - Boolean value telling the addon that it is
							the winner of a network negotiation. Usually that
							will imply that it has the best data, and so must
							send them to the other addons.
			cbIS	- Callback: This function will receive a timestamp for each
					  incoming info that is bound for your addon.
							function cbIS(time)
							time - Numeric value denoting the timestamp of last
							incoming data.



NOTE: All the following functions return a boolean value (true/false)
	  indicating if the operation was carried out or not.


Clear transmission buffer
~~~~~~~~~~~~~~~~~~~~~~~~~
	Before any new transmission, the output buffer must be cleared. This is
	done with the function:
		DuckNet_ClearOutput(prefix)

	When this function has been executed, DuckNet is ready to receive data
	to transmit from the addon.

Send an entire table
~~~~~~~~~~~~~~~~~~~~
	In many cases an entire table can be sent directly. Simply put the table
	in this function and wait for the callback saying it is done.
		DuckNet_SendTable(prefix,table)
		
	Restrictions:
	DuckNet can only send tables, numeric values, strings, and boolean data.
	There are also restrictions on the size of entries. This will vary depending
	on usage. Try to keep your strings below 200 characters, and the nesting
	of tables as shallow as possible. This will also ensure a much faster
	transmission.

Advance line in output buffer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	When your addon is done building a line of data, it must call this
	function to advance the line-counter. This is optional on the last line.
		DuckNet_NewLine(prefix)

Add a new key/action/marker
~~~~~~~~~~~~~~~~~~~~~~~~~~~
	This will add addon-specific information to the current output buffer. The
	"name" is a single character that will act as a name/type/tag for the info
	that accompanies it.
		DuckNet_AddKey(prefix,name,info)

Add a new table name
~~~~~~~~~~~~~~~~~~~~
	Call this function to include the name of a table in the output buffer, and
	make the reciever set that table as the new root for the rest of the
	transmitting line.
		DuckNet_AddTable(prefix,name)

Add a new entry with data
~~~~~~~~~~~~~~~~~~~~~~~~~
	Call this function to add a variable to be placed in the current table
	root.
		DuckNet_AddEntry(prefix,name,data)

Start transmission
~~~~~~~~~~~~~~~~~~
	After the multi-line output buffer has been build, call this function to
	start the actual transmission. Your addon will be notified when it has
	finished by calling the callback function.
		DuckNet_DoTransmission(prefix)

Single-line transmission
~~~~~~~~~~~~~~~~~~~~~~~~
	This is useful for sending simple messages to other addons, and will
	transmit single-line data after it has been built in the normal manner.
		DuckNet_SingleTransmit(prefix)

Table copy
~~~~~~~~~~
	To copy one of your tables, you are welcome to use DuckNet's function. This
	function will return a pointer to a new table.
		DuckNet_CopyTable(table)

*****************************************************************************]]

BINDING_HEADER_DUCKNET = "DuckNet bindings";
BINDING_NAME_TOGGLEDUCKNET = "Toggle DuckNet window";
DUCKNET_VERSIONTEXT = "DuckNet v1.09";
DUCKNET_VERSION_DATA = "003";	-- The data structure in use
DUCKNET_VERSION_API = 2;
SLASH_DUCKNET1 = "/ducknet";
SLASH_DUCKNET2 = "/dn";
DUCKNET_FROMCODE = "|";
DUCKNET_TOCODE = "�";
DUCKNET_LINEBREAKnative = "\n";
DUCKNET_LINEBREAKhtml = "<BR/>";
DUCKNET_ENTRYBREAKCONTINUE = "<BREAKFORMORE/>";
DUCKNET_BOOLVALDATA="BoolVal:";

--[[	DB structure

["<prefix>"] = {
	CType = "addon channel-type",						-- Addon-channel to use for this prefix
	Idle = true/false,                                  -- Receiving or transmitting multilined data
	LastOutput = "<last complete transmitted line>",	-- Used for discriminating own transmissions
	LastEntry=-1,                                       -- Last multilined entry received
	Negotiate = {										-- Random negotiation
		Start=false,									-- Don't start negotiation
		HoldOff=0,										-- Timer to wait to evaluate all incoming stamps and rolls
		SelfStamp=0,									-- The stamp I have
		HighestStamp=0,									-- The highest I've seen from others
		Highest=0,                                      -- Highest random number so far
		Roll=-1,                                        -- I am not highest
	},

    Callback = {
		RX = <pointer to callback function>					-- Function to call when data has been received
		TX = <pointer to callback function>					-- Function to call when data has been transmitted
		Info = <pointer to callback function>				-- Function to call when information has been received
		CheckStamp = <pointer to callback function>			-- Function to call when a stamp is needed
		NegotiateWon = <pointer to callback function>		-- Function to call when a negotiation is finished
	},

	Info = {												-- Headers, version info, actions/commands
		[1] = {
            Type = <single-char type>,
			Command = <followinf info>,
		},
		[2] = {
            Type = <single-char type>,
			Command = <followinf info>,
		},
		[n] = {
            Type = <single-char type>,
			Command = <followinf info>,
		},
	},

	Data = {												-- Receive table
	},

	TransmitDelay = 0,										-- Delay before starting the actual transmission
	Transmitting = DUCKNET_TRANS_STOP,						-- Line-counter
	Output = {												-- Transmission buffer
		Tables[] = <array of tables>						-- All tables inserted in sequential order for rebuild at linebreaks
		Line = <current line number>
		Entry = <entry-counter>
		Buffer = {
		},
	},
},

]]

local DUCKNET_NEG_STOPPED = -1;
local DUCKNET_NEG_RUNNING = 0;
local DUCKNET_NEG_DONE = 5;

local DUCKNET_HEARTBEAT = 0.3;
local DUCKNET_TRANS_STOP = 0;
local DUCKNET_TRANS_START = 1;

DUCKNET_REQ_NEWDATA = "DuckNet-Req-New-Data";
DUCKNET_REQ_RETRANSMIT = "DuckNet-Req-Retransmit-Data";
DUCKNET_ACT_TRANSMIT = "DuckNet-Act-Transmit-Data";
DUCKNET_ACT_TRANSMITDONE = "DuckNet-Act-Transmit-Data-Done";
DUCKNET_ACT_MYSTAMP = "DuckNet-Act-MyStamp";

local DuckNet_Timers = {
	Last=0,
	LastEntry=0,
};
DuckNet_LS = { IconPosition = 100, };
local DuckNet_DB = { };
local DuckNet_Hooked = false;
local DuckNet_Debug = false;
local DuckNet_DebugData = false;
local DuckNet_Rebuilding = nil;


-- Table-copy for use by other addons (not tested for table depth. yet.)
function DuckNet_CopyTable(t)
	local new = {};          
	local i,v;  
	for i,v in pairs(t) do
		if (type(v)=="table") then new[i]=DuckNet_CopyTable(v); else new[i]=v; end
	end
	return new;
end

-- From LUA 5.1, the scheme for table references changed a bit, and in
-- some cases can make life difficult for programmers when passing table
-- references to functions.
-- This function can be used to clear a table given the reference only.
function DuckNet_ClearTable(t)
	for k in pairs(t) do t[k]=nil; end;
end


-- Set up for handling
function DuckNet_OnLoad()
	this:RegisterEvent("VARIABLES_LOADED");
	this:RegisterEvent("CHAT_MSG_ADDON");			-- Addon channel

	SlashCmdList["DUCKNET"]=function(msg) DuckNet_Slasher(msg) end;
	tinsert(UISpecialFrames,"DuckNet_AFrame");
	
	-- As LUA random can be dodgy at times, it is recommended to make a few calls to it to "get it going"
	math.randomseed(time()); math.random(); math.random(); math.random();
	
	DuckNet_Chat("Loaded");
end


function DuckNet_UnRegister(prefix)
	DuckNet_DB[prefix]=nil;
end


--[[    Minimap icon stuff    ]]
-- Thanks to Yatlas and Gello for the initial code
function DuckNet_BeingDragged()
	-- Thanks to Gello for this code
	local xpos,ypos = GetCursorPosition();
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	if (IsShiftKeyDown()) then
		DuckNet_LS.IconPosition=nil;
		xpos=(xpos/UIParent:GetScale()-xmin)-16;
		ypos=(ypos/UIParent:GetScale()-ymin)+16;
		DuckNet_SetIconAbsolute(xpos,ypos);
		return;
	end
	DuckNet_LS.IconX=nil;
	DuckNet_LS.IconY=nil;

	xpos=xmin-xpos/UIParent:GetScale()+70
	ypos=ypos/UIParent:GetScale()-ymin-70

	DuckNet_SetIconAngle(math.deg(math.atan2(ypos,xpos)));
end


function DuckNet_SetIconAngle(v)
	if (v<0) then v=v+360; end
	if (v>=360) then v=v-360; end

	DuckNet_LS.IconPosition=v;
	DuckNet_MinimapIcon:SetPoint("TOPLEFT","Minimap","TOPLEFT",54-(78*cos(DuckNet_LS.IconPosition)),(78*sin(DuckNet_LS.IconPosition))-55);
	DuckNet_MinimapIcon:Show();
end


function DuckNet_SetIconAbsolute(x,y)
	DuckNet_LS.IconX=x;
	DuckNet_LS.IconY=y;

	DuckNet_MinimapIcon:SetPoint("TOPLEFT","Minimap","BOTTOMLEFT",x,y);
end



-- There's slashing to be done
function DuckNet_Slasher(msg)
-- Validate input
	if (not msg) then msg=""; end
	if (strlen(msg)>0) then msg=strlower(msg); end
	
	if (msg=="debug") then
		if (DuckNet_Debug==true) then DuckNet_Debug=false; DuckNet_Chat("DEBUG: OFF");
		elseif (DuckNet_Debug==false) then DuckNet_Debug=true; DuckNet_Chat("DEBUG: ON"); end;
		return;
	end;

	if (msg=="debugdata") then
		if (DuckNet_DebugData==true) then DuckNet_DebugData=false; DuckNet_Chat("DEBUG: Incoming data not displayed");
		elseif (DuckNet_DebugData==false) then DuckNet_DebugData=true; DuckNet_Chat("DEBUG: Incoming data will be displayed in chat"); end;
		return;
	end;

	DuckNet_ToggleMainFrame();
end


-- Duh
function DuckNet_ToggleMainFrame()
	if (DuckNet_AFrame:IsVisible()) then
		DuckNet_AFrame:Hide();
	else
		DuckNet_AFrame:Show();
	end
end

function DuckNet_Frame_OnShow()
end

function DuckNet_Frame_OnHide()
end


function DuckNet_Valid(prefix,ctype)
	if (not prefix) then return false; end							-- None provided
	if (not DuckNet_DB[prefix]) then return false; end				-- Provided, but not registered
	if (ctype) then
		if (DuckNet_DB[prefix].CType~=ctype) then return false; end	-- Wrong channel type
	end
	return true;
end


-- The heartbeat. Called from update of the minimap button or the hosting addon, if any.
function DuckNet_HeartBeat(elapsed)
	if (not elapsed) then return; end;

	DuckNet_Timers.Last=DuckNet_Timers.Last+elapsed;				-- Update this
	if (DuckNet_Timers.Last<DUCKNET_HEARTBEAT) then return; end;	-- Heartbeat is 500ms
	DuckNet_Timers.Last=0;											-- Restart
    
	for k,v in pairs(DuckNet_DB) do
		DuckNet_HeartBeatCycle(k);
	end

	-- Note that we're not using the accurate form. The reason is that this code
	-- does not need accurate timing, and this way has a much bigger chance of
	-- pausing if any hick-ups should occur elsewhere - thus increasing the chance
	-- of correct transmission.
end


-- Handler for a single prefix
function DuckNet_HeartBeatCycle(prefix)
	if (not DuckNet_Valid(prefix)) then
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet_HeartBeatCycle called with prefix "..prefix); end;
		return 1;					-- Stop cycle
	end;

	local now=time();

--[[		DuckNet negotiation		]]
	if (DuckNet_DB[prefix].Negotiate.Start) then
		DuckNet_DB[prefix].Negotiate.HoldOff=DUCKNET_NEG_RUNNING;
		DuckNet_DB[prefix].Negotiate.Start=false;
	end
	if (DuckNet_DB[prefix].Negotiate.HoldOff>=DUCKNET_NEG_RUNNING) then
		DuckNet_DB[prefix].Negotiate.HoldOff=DuckNet_DB[prefix].Negotiate.HoldOff+DUCKNET_HEARTBEAT;
		if (DuckNet_DB[prefix].Negotiate.HoldOff>=DUCKNET_NEG_DONE) then
			if (DuckNet_DB[prefix].CallBack.NegotiateWon) then		-- Check for callback-function
				local IWon=false;
				if (DuckNet_DB[prefix].Negotiate.SelfStamp>=DuckNet_DB[prefix].Negotiate.HighestStamp) then				-- I have highest stamp
					if (DuckNet_DB[prefix].Negotiate.HighestRoll<DuckNet_DB[prefix].Negotiate.Roll) then IWon=true; end;	-- I have highest roll
				end
				if (DuckNet_Debug) then
					if (IWon) then DuckNet_Chat(prefix.." DEBUG: Negotiation won");
					else DuckNet_Chat(prefix.." DEBUG: Negotiation lost"); end;
				end;
				DuckNet_DB[prefix].CallBack.NegotiateWon(IWon,DuckNet_DB[prefix].Negotiate.StartMarker);		-- Tell the calling addon if it won or not
				if (DuckMod_Present) then DuckMod_DN_NegotiationComplete(IWon); end
			elseif (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Negotiation done");
			end
			DuckNet_StopNegotiation(prefix);
		end
	end

	if (DuckNet_DB[prefix].Transmitting>DUCKNET_TRANS_STOP) then	-- It's transmitting
		if (DuckNet_DB[prefix].TransmitDelay>0) then
			DuckNet_DB[prefix].TransmitDelay=DuckNet_DB[prefix].TransmitDelay-DUCKNET_HEARTBEAT;
		elseif (not DuckNet_DB[prefix].LastOutput) then					-- It's there now
			DuckNet_SendNextLine(prefix);							-- Duh
		end
	end

	-- Check broken incoming data
	if (DuckNet_DB[prefix].LastEntry>-1 and now-DuckNet_Timers.LastEntry>10) then
		DuckNet_DB[prefix].LastEntry=-1;								-- Stop receiving
	end
	
	return 0;
end


-- An event has been received
function DuckNet_OnEvent(event)
	if (event=="CHAT_MSG_ADDON") then
		local instance=nil;
		if (DuckMod_Present) then instance=DuckMod_DN_CHAT_MSG_ADDON(arg1,arg2,arg3,arg4); else instance=arg4; end
		if (not DuckNet_Valid(arg1,arg3)) then return; end;					-- Quick way out without further function-calls (which DuckNet_ParseInput will do)
		DuckNet_ParseInput(arg1,arg2,instance);								-- It's from a registered channel, so attempt to decode it
		return;
	end

	-- Check basic stuff needed
	if (event=="VARIABLES_LOADED") then
		if (DuckNet_LS.IconX and DuckNet_LS.IconY) then DuckNet_SetIconAbsolute(DuckNet_LS.IconX,DuckNet_LS.IconY);
		else DuckNet_SetIconAngle(DuckNet_LS.IconPosition); end
		return;
	end
end


-- Display a message in the chat-pane
function DuckNet_Chat(msg,r,g,b)
	if (DEFAULT_CHAT_FRAME) then
		if (not r and not g and not b) then r=1; g=0; b=1; end;
		if (not r) then r=0; end;
		if (not g) then g=0; end;
		if (not b) then b=0; end;
		DEFAULT_CHAT_FRAME:AddMessage("DuckNet: "..msg,r,g,b);
	end
end


-- Handles basic set-up for network negotiation
function DuckNet_StartNegotiation(prefix,stampmarker,selfstamp,linestamp)
	DuckNet_DB[prefix].Negotiate.StartMarker=stampmarker;
	DuckNet_DB[prefix].Negotiate.SelfStamp=selfstamp;
	if (not linestamp) then linestamp=selfstamp; end;
	DuckNet_DB[prefix].Negotiate.Start=true;											-- Start a negotiation
	DuckNet_DB[prefix].Negotiate.Roll=0;												-- Do not participate in roll yet
	DuckNet_DB[prefix].Negotiate.HighestStamp=0;
	DuckNet_DB[prefix].Negotiate.HighestRoll=0;

	if (selfstamp<=linestamp) then														--> I have same or lower
		DuckNet_DB[prefix].Negotiate.HighestStamp=linestamp;							-- I am not higher, so set incoming as highest
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: NegStart -> I have same or lower stamp (my poll or incoming)"); end;
		return false;			-- Did not send data
	end

	DuckNet_DB[prefix].Negotiate.HighestStamp=selfstamp;							-- I am higher, so set it as highest
	DuckNet_DB[prefix].Negotiate.Roll=math.random(1000000);							-- Make my own random for transmission
	local output="";																-- Start line for transmission
	if (stampmarker) then output=output..DuckNet_MakeEntry("M",stampmarker); end	-- Add stampmarker if it has been received
	output=output..DuckNet_MakeEntry("S",DuckNet_DB[prefix].Negotiate.SelfStamp);	-- Add own stamp
	output=output..DuckNet_MakeEntry("R",DuckNet_DB[prefix].Negotiate.Roll);		-- Add own random number
	output=output..DuckNet_MakeEntry("A",DUCKNET_ACT_MYSTAMP);						-- Set action
	DuckNet_Out(prefix,output);														-- Send your random
	if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: NegStart -> Roll: "..DuckNet_DB[prefix].Negotiate.Roll); end;
	return true;				-- Data sent
end


-- Kill it if need be
function DuckNet_StopNegotiation(prefix)
	DuckNet_DB[prefix].Negotiate.HighestStamp=0;
	DuckNet_DB[prefix].Negotiate.HighestRoll=0;
	DuckNet_DB[prefix].Negotiate.Start=false;								-- Don't start a negotiation
	DuckNet_DB[prefix].Negotiate.HoldOff=DUCKNET_NEG_STOPPED;				-- No negotiation is running
end


--[[ DuckNet Protocol 1 ]]
function DuckNet_ParseInput(prefix,text,instance)
	function Closest()
		local small=string.find(text,"!"); if (not small) then small=10000; end
		local Colon=string.find(text,":"); if (not Colon) then Colon=10000; end
		local Hash=string.find(text,"#"); if (not Hash) then Hash=10000; end
		if (Colon<small) then small=Colon; end
		if (Hash<small) then small=Hash; end
		return small;
	end
	function Next()
		local code=nil;
		local info=nil;
		local data=nil;
--[[		Commands			]]
		if (string.sub(text,1,1)==":") then
			code=string.sub(text,1,2); text=string.sub(text,3);		-- Extract command
			local near=Closest();
			if (near>1) then info=string.sub(text,1,near-1); text=string.sub(text,near);		-- Extract the info
			else info=text; text=""; end														-- Use the rest of the line as info
--[[		Table entry			]]
		elseif (string.sub(text,1,1)=="#") then
			code=string.sub(text,1,2); text=string.sub(text,3);									-- Extract tag and type-indicator
			local near=Closest(); if (near>1) then												-- Locate next delimiter
				info=string.sub(text,1,near-1); text=string.sub(text,near);						-- Extract entry name
				if (string.sub(text,1,1)=="!") then
					text=string.sub(text,2);													-- Remove data-marker if data is present
					if (string.sub(text,1,1)=="\"") then
						text=string.sub(text,2); near=string.find(text,"\"");					-- Remove first quote. Find next quote
						if (not near) then text="\""..text; data=text; text="";					-- Not there. Reinsert previous. Copy. Save.
						else data=string.sub(text,1,near-1); text=string.sub(text,near+1);		-- Get it and remove including next quote
						end
					else
						near=Closest();				-- Not string, get next delimiter
						if (near==1) then data=nil;
						elseif (near>1) then data=tonumber(string.sub(text,1,near-1)); text=string.sub(text,near);
						else data=tonumber(text); text=""; end
					end
				end
			else info=text; text=""; end		-- Use all
		else info=text; text=""; end			-- Use all
		return code,info,data;
	end


--[[	Validate input	]]
	if (DuckNet_Valid(prefix)==false) then return; end		-- Validate incoming prefix
	if (text==DuckNet_DB[prefix].LastOutput) then DuckNet_DB[prefix].LastOutput=nil; return; end	-- Own transmission received
	if (not text) then return; end
	text=DuckNet_SwapText(text,DUCKNET_TOCODE,DUCKNET_FROMCODE);

	-- Check for broken lines for concatenation
	local broken=nil;
	if (string.find(text,DUCKNET_ENTRYBREAKCONTINUE)==string.len(text)-(string.len(DUCKNET_ENTRYBREAKCONTINUE)-1)) then		-- Break-mark
		broken=string.sub(text,1,string.len(text)-string.len(DUCKNET_ENTRYBREAKCONTINUE));
	end
	if (broken and not DuckNet_Rebuilding) then DuckNet_Rebuilding=""; end				-- Start rebuilding
	if (DuckNet_Rebuilding) then														-- We are concatenating something
		if (broken) then DuckNet_Rebuilding=DuckNet_Rebuilding..broken; return; end		-- There's more coming
		text=DuckNet_Rebuilding..text;													-- We're done now
		DuckNet_Rebuilding=nil;															-- Go back to normal operation
	end

	-- Set-up for decoding
	local now=time();
	if (DuckNet_DB[prefix].CallBack.InStamp) then DuckNet_DB[prefix].CallBack.InStamp(now); end

	local linestamp=0;
	local tp=DuckNet_DB[prefix].Data;					-- Pointer to the table root
	DuckNet_DB[prefix].Info={ };						-- Clear the info-table
	local infoline=1;									-- To build the info table
	local stampmarker=nil;								-- Stamp marker for this line
	local lastroll=0;

--[[	Cycle through the entire line of input	]]
	while(string.len(text)) do
		local tag,entry,data=Next();					-- Extract next entry
		if (not tag) then break; end;					-- Some tag-less info
		local entryn=tonumber(entry);					-- Better execution

		--[[                    ]]
		--[[   Common entries   ]]
		--[[                    ]]

		--[[ Stamp ]]
		if (tag==":S") then
			linestamp=entryn;

		--[[ Random-data ]]
		elseif (tag==":R") then
			lastroll=entryn;
			if (DuckNet_DB[prefix].Negotiate.HighestRoll<entryn) then
				DuckNet_DB[prefix].Negotiate.HighestRoll=entryn;
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: High roll received: "..entryn.." (My roll: "..DuckNet_DB[prefix].Negotiate.Roll..")");
			elseif (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Low roll received: "..entryn.." (My roll: "..DuckNet_DB[prefix].Negotiate.Roll..")"); end
			end

		--[[ Stamp tag/marker ]]
		elseif (tag==":M") then
			stampmarker=entry;
			DuckNet_DB[prefix].Negotiate.SelfStamp=DuckNet_DB[prefix].CallBack.CheckStamp(entry);		-- Get my addon's stamp
			if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Marker received: "..entry); end
		end

		--[[ Incoming entry sequence (numeric) ]]
		if (tag==":E") then
			entry=entryn;
			DuckNet_Timers.LastEntry=now;
			if (entry~=DuckNet_DB[prefix].LastEntry+1) then												-- Sequence is broken
				DuckNet_Out(prefix,":A"..DUCKNET_REQ_RETRANSMIT);		-- Request the data retransmitted
			else
				DuckNet_DB[prefix].LastEntry=DuckNet_DB[prefix].LastEntry+1;							-- Increase sequence
			end

		--[[ DuckNet handling ]]
		elseif (tag==":A" and entry==DUCKNET_REQ_RETRANSMIT) then					-- An addon received inconsistency and requires a re-transmit
			if (DuckNet_DB[prefix].Transmitting>DUCKNET_TRANS_STOP) then			-- I am currently transmitting
				DuckNet_DB[prefix].Transmitting=DUCKNET_TRANS_START;				-- Start from the top
				DuckNet_DB[prefix].TransmitDelay=2;									-- 2 seconds delay for retransmission
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Restarting transmission"); end
			end

		--[[ Someone is gonna send stuff ]]
		elseif (tag==":A" and entry==DUCKNET_ACT_TRANSMIT) then
			DuckNet_StopNegotiation(prefix);
			DuckNet_Timers.LastEntry=now;
			DuckNet_DB[prefix].LastEntry=0;													-- Nothing received yet
            DuckNet_DB[prefix].Data={ };													-- Clear input buffer
			if (DuckNet_DB[prefix].Transmitting>=DUCKNET_TRANS_START) then					-- I am transmitting too
				DuckNet_DB[prefix].Transmitting=DUCKNET_TRANS_STOP;							-- Stop transmission as several is trying to send
				DuckNet_DB[prefix].TransmitDelay=0;											-- No delay
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Some other are sending while I am sending (or I am about to send). Aborting my transmission."); end;
				DuckNet_DB[prefix].CallBack.TX(DuckNet_DB[prefix].Transmitting,instance);	-- Notify addon about abort
			else 
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Incoming data..."); end
				if (DuckMod_Present) then DuckMod_DN_ACT_TRANSMIT(instance,stampmarker,linestamp); end
			end

		--[[ Inbound transmission is finished ]]
		elseif (tag==":A" and entry==DUCKNET_ACT_TRANSMITDONE) then
			if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Incoming data done"); end
			DuckNet_DB[prefix].CallBack.RX(DuckNet_DB[prefix].Data);				-- Give table-pointer to registered addon
			DuckNet_DB[prefix].LastEntry=-1;										-- Nothing received yet
			if (DuckMod_Present) then DuckMod_DN_ACT_TRANSMITDONE(instance); end

		--[[ An addon has newer data than someone else ]]
		elseif (tag==":A" and entry==DUCKNET_ACT_MYSTAMP) then						-- Some addon informs about it's stamp
			if (DuckMod_Present) then DuckMod_DN_Negotiation(instance,stampmarker,linestamp,lastroll); end
			if (linestamp>DuckNet_DB[prefix].Negotiate.HighestStamp) then			-- New stamp is highest of all
				DuckNet_DB[prefix].Negotiate.HighestStamp=linestamp;				-- Save it for for test after negotiation timer
				DuckNet_DB[prefix].Negotiate.HighestRoll=lastroll;					-- Higher stamp, so set it's roll as highest
			end
			DuckNet_DB[prefix].Negotiate.HoldOff=DUCKNET_NEG_RUNNING;				-- Restart negotiation timer

			DuckNet_DB[prefix].Negotiate.SelfStamp=DuckNet_DB[prefix].CallBack.CheckStamp(stampmarker);	-- Someone sends their stamp, so get my addon's stamp
			if (DuckNet_DB[prefix].Negotiate.SelfStamp>DuckNet_DB[prefix].Negotiate.HighestStamp) then
				DuckNet_DB[prefix].Negotiate.Start=true;							-- Restart negotiating
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: ACT_MYSTAMP -> Lower stamp received. Restarting negotiation timer."); end
			elseif (DuckNet_DB[prefix].Negotiate.SelfStamp<DuckNet_DB[prefix].Negotiate.HighestStamp) then
				DuckNet_StopNegotiation(prefix);
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: ACT_MYSTAMP -> Higher stamp received. Stopping my negotiation."); end
			elseif (DuckNet_Debug) then
				DuckNet_Chat(prefix.." DEBUG: ACT_MYSTAMP -> Equal stamp received with roll: "..lastroll.." - High roll is: "..DuckNet_DB[prefix].Negotiate.HighestRoll);
			end;

		--[[ An addon wanna know whazzup ]]
		elseif (tag==":A" and entry==DUCKNET_REQ_NEWDATA) then
			if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Incoming request for transmit of data"); end
			if (DuckMod_Present) then DuckMod_DN_REQ_NEWDATA(instance,stampmarker,linestamp); end
			if (DuckNet_DB[prefix].Negotiate.Start or DuckNet_DB[prefix].Negotiate.HoldOff~=DUCKNET_NEG_STOPPED) then
				if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: REQ_NEWDATA -> Already negotiating. Stopping all current negotiation."); end
				DuckNet_StopNegotiation(prefix);
			else
				local thestamp=DuckNet_DB[prefix].CallBack.CheckStamp(stampmarker);
				local sentdata=DuckNet_StartNegotiation(prefix,stampmarker,thestamp,linestamp);
				if (DuckMod_Present and sentdata) then DuckMod_DN_Negotiation(instance,stampmarker,thestamp,DuckNet_DB[prefix].Negotiate.Roll); DuckMod_DN_Neg_SetMyStamp(thestamp); end
			end

			--[[ Commands to send to calling addon ]]
		elseif (string.sub(tag,1,1)==":") then											-- Any command other than entry-number
			local tagtype=string.sub(tag,2,2);											-- Get the letter
			DuckNet_DB[prefix].Info[infoline]={};										-- Make (and clear) table
			DuckNet_DB[prefix].Info[infoline].Type=tagtype;								-- Save the command type
			if (tag==":S") then
				DuckNet_DB[prefix].Info[infoline].Command=entryn;						-- Save the numeric command contents
			else
				DuckNet_DB[prefix].Info[infoline].Command=entry;						-- Save the command contents
			end
			infoline=infoline+1;
			if (DuckMod_Present) then DuckMod_Tag(prefix,instance,tag,entry); end
		end


--[[	Incoming table data 	]]
		if (string.sub(tag,1,1)=="#") then						-- >> Entry
			if (string.sub(tag,2,2)=="n") then					-- The entry-name is numeric
				entry=entryn;									-- Convert it to a number
			end
			if (data) then
				if (not DuckNet_Numeric(type(data))) then
					if (string.find(data,DUCKNET_BOOLVALDATA.."true")==1) then data=true;				-- Set boolean 'true'
					elseif (string.find(data,DUCKNET_BOOLVALDATA.."false")==1) then data=false;			-- Set boolean 'false'
					else data=DuckNet_SwapText(data,DUCKNET_LINEBREAKhtml,DUCKNET_LINEBREAKnative);		-- Recreate string
					end
				end
				tp[entry]=data;	    							-- There's data, so store it in the entry as a label
			else												-- There's no data, so we're building (or adding to) a table below
				if (not tp[entry]) then tp[entry]={}; end		-- Generate empty table
				tp=tp[entry];									-- Set new table pointer
			end;
		end
	end   -- Text-loop

	-- The line contained info, so tell the addon
	if (infoline>1 and DuckNet_DB[prefix].CallBack.Info) then
		DuckNet_DB[prefix].CallBack.Info(DuckNet_DB[prefix].Info);
	end
end


function DuckNet_MakeBool(value)
	if (type(value)=="boolean") then return value; end
	if (not value) then return false; end
	if (type(value)=="string") then value=tonumber(value); end
	if (type(value)=="number") then
		if (value==0) then return false; end
		return true;
	end
	return false;
end


-- Entry-builder
function DuckNet_MakeEntry(code,entry,data)
	if (not code) then return entry; end				-- Just return it
	if (not entry) then entry=tonumber(0); end			-- Just make sure it's a number

	local text="";
	local numeric=DuckNet_Numeric(type(entry));

	if (code=="#") then									-- It's an entry
		text=text.."#";									-- Set entry marker
		if (numeric) then text=text.."n";				-- It's a numeric entry
		else text=text.."s"; end						-- It's a string entry
	else text=text..":"..code; end						-- It's (probably) a command

	text=text..entry;									-- Add entry for supplied code

	if (code=="#" and data) then														-- The entry carries data
		numeric=DuckNet_Numeric(type(data));											-- Check numeric
		local bool=DuckNet_Bool(type(data));											-- Check boolean
		text=text.."!";																	-- Denote data
		if (not numeric) then															-- A string, so enclose
			text=text.."\"";
			if (bool) then
				if (data==true) then data=DUCKNET_BOOLVALDATA.."true"; else data=DUCKNET_BOOLVALDATA.."false"; end
			else
				data=DuckNet_SwapText(data,DUCKNET_LINEBREAKnative,DUCKNET_LINEBREAKhtml);	-- Transmittable linebreaks
			end
		end
		text=text..data;																-- Add the entry data
		if (not numeric) then text=text.."\""; end										-- A string, so enclose
	end

	return text;
end


-- Handle further transmission
function DuckNet_SendNextLine(prefix)
	if (not DuckNet_DB[prefix].Output.Buffer[DuckNet_DB[prefix].Transmitting]) then		-- No more lines
		if (DuckNet_DB[prefix].PB) then DuckNet_DB[prefix].PB:SetValue(0); end
		local lines=DuckNet_DB[prefix].Transmitting;
		DuckNet_DB[prefix].Transmitting=DUCKNET_TRANS_STOP;								-- Not transmitting
		DuckNet_DB[prefix].CallBack.TX(lines);											-- Notify addon
		return;
	end
	
	if (DuckNet_DB[prefix].PB) then DuckNet_DB[prefix].PB:SetValue(DuckNet_DB[prefix].Transmitting); end
	DuckNet_Out(prefix,DuckNet_DB[prefix].Output.Buffer[DuckNet_DB[prefix].Transmitting]);		-- Send it
	DuckNet_DB[prefix].Transmitting=DuckNet_DB[prefix].Transmitting+1;												-- Go to next
end


-- Generic addon output
function DuckNet_Out(prefix,text)
	text=DuckNet_SwapText(text,DUCKNET_FROMCODE,DUCKNET_TOCODE);
	SendAddonMessage(prefix,text,DuckNet_DB[prefix].CType);

	DuckNet_DB[prefix].LastOutput=text;

	if (DuckNet_DebugData) then DuckNet_Chat("! "..prefix.." "..text); end
end


--[[                   ]]
--[[ Support-functions ]]
--[[                   ]]

function DuckNet_SplitRequestMarker(marker)
	if (not marker) then return nil; end
	if (not string.find(marker,",")) then return marker; end

	local first=nil;
	local second=nil;
	local third=nil;
	local fourth=nil;

	first=string.sub(marker,1,string.find(marker,",")-1); marker=string.sub(marker,string.find(marker,",")+1);
	if (string.find(marker,",")) then
		second=string.sub(marker,1,string.find(marker,",")-1); marker=string.sub(marker,string.find(marker,",")+1);
		if (string.find(marker,",")) then
			third=string.sub(marker,1,string.find(marker,",")-1); marker=string.sub(marker,string.find(marker,",")+1);
			fourth=marker;
		else third=marker; end
	else second=marker; end

	return first,second,third,fourth;
end

-- This function may seem a little overkill for a trained LUA programmer, but
-- better safe than sorry in case of any spec-changes
function DuckNet_SwapText(text,from,to)
	if (text) then
		while (string.find(text,from)) do text=string.gsub(text,from,to); end
	end
	return text;
end

-- Yeye... I know LUA natively does this (mostly), but this one restricts it a bit
function DuckNet_Numeric(typestring)
	if (typestring=="number" or typestring=="nil") then return true; end
	return false;
end
function DuckNet_Bool(typestring)
	if (typestring=="boolean") then return true; end
	return false;
end


--[[                                                                  ]]
--[[ Please note: The following functions are still at a design stage ]]
--[[                                                                  ]]

function DuckNet_AutoTableLinebreak(prefix,startnext)
	DuckNet_NewLine(prefix);													-- Advance

	if (table.maxn(DuckNet_DB[prefix].Output.Tables)<1) then
		if (startnext) then
			tp[DuckNet_DB[prefix].Output.Line]=prefix..":E"..DuckNet_DB[prefix].Output.Entry;
			DuckNet_DB[prefix].Output.Entry=DuckNet_DB[prefix].Output.Entry+1;
		end
	else
		for i,k in DuckNet_DB[prefix].Output.Tables do							-- Iterate previous subtables
			if (not DuckNet_AddEntry(prefix,k,nil,true)) then return false; end	-- Re-add them at next line
		end
	end
end


function DuckNet_CodeTable(prefix,t,level)
	local i,v;
	for i,v in t do
		if (level==0) then DuckNet_NewLine(prefix); end								-- We're at the root and starting new entry, so make a new line
		if (type(v)=="table") then
			if (not DuckNet_AddTable(prefix,i)) then return false; end				-- Add name of sub-table
			if (not DuckNet_CodeTable(prefix,v,level+1)) then return false; end		-- Code sub-table
		else
			if (not DuckNet_AddEntry(prefix,i,v)) then return false; end			-- Standard entry, so just add it with data
		end
	end

	table.remove(DuckNet_DB[prefix].Output.Tables);			-- Remove last table (default is last entry)
	DuckNet_AutoTableLinebreak(prefix);

	return true;
end


--[[  Checks if DuckNet is busy ]]
function DuckNet_NotCrusial(prefix)
	if (not DuckNet_Valid(prefix)) then return false; end;	                            	-- No such prefix
	if (DuckNet_DB[prefix].LastEntry>-1) then return false; end;							-- I am currently receiving
	if (DuckNet_DB[prefix].Transmitting>=DUCKNET_TRANS_START) then return false; end;		-- I am currently transmitting
	if (DuckNet_DB[prefix].Negotiate.Start) then return false; end;							-- I am currently starting a negotiation
	return true;
end



--[[                    ]]
--[[      User API      ]]
--[[                    ]]


function DuckNet_GetHook()
	if (DuckNet_Hooked==true) then return nil; end
	DuckNet_Hooked=true;
	DuckNet_MinimapIcon:Hide();
	return DuckNet_HeartBeat;
end


function DuckNet_Register(prefix,ctype,cbRX,cbTX,cbINFO,cbCS,cbNW,cbIS,pb)
	if (DuckMod_Present) then DuckMod_OnLoad(); end

	DuckNet_UnRegister(prefix);
	if not (ctype=="PARTY" or ctype=="RAID" or ctype=="GUILD" or ctype=="BATTLEGROUND") then return false; end;
	DuckNet_DB[prefix]={ };
	DuckNet_DB[prefix].CType=ctype;
	DuckNet_DB[prefix].PB=pb;
	DuckNet_DB[prefix].LastOutput=nil;
	DuckNet_DB[prefix].LastEntry=-1;
	DuckNet_DB[prefix].Negotiate={ };
	DuckNet_DB[prefix].Negotiate.Start=false;
	DuckNet_DB[prefix].Negotiate.HoldOff=DUCKNET_NEG_STOPPED;
	DuckNet_DB[prefix].Negotiate.SelfStamp=0;
	DuckNet_DB[prefix].Negotiate.HighestStamp=0;
	DuckNet_DB[prefix].Negotiate.HighestRoll=0;
	DuckNet_DB[prefix].Negotiate.Roll=-1;
	DuckNet_DB[prefix].CallBack={ };
	DuckNet_DB[prefix].CallBack.RX=cbRX;
	DuckNet_DB[prefix].CallBack.TX=cbTX;
	DuckNet_DB[prefix].CallBack.Info=cbINFO;
	DuckNet_DB[prefix].CallBack.CheckStamp=cbCS;
	DuckNet_DB[prefix].CallBack.NegotiateWon=cbNW;
	DuckNet_DB[prefix].CallBack.InStamp=cbIS;
	DuckNet_DB[prefix].Info={ };
	DuckNet_DB[prefix].Data={ };
	DuckNet_DB[prefix].Output = { };
	DuckNet_DB[prefix].Output.Buffer = { };
	DuckNet_DB[prefix].Output.Tables = { };

	DuckNet_DB[prefix].TransmitDelay=0;						-- No delay
	DuckNet_DB[prefix].Transmitting=DUCKNET_TRANS_STOP;		-- Not transmitting
	DuckNet_DB[prefix].Idle=true;							-- Ready to work now

	return true;
end


-- Return true: Data will be sent shortly
-- Return false: Data can't be sent now
-- Return nil: Data is unsendable
function DuckNet_SendTable(prefix,ADD_Data,ADD_BlockName,ADD_Stamp)
	if (not ADD_Data) then return nil; end
	if (not ADD_BlockName) then ADD_BlockName=prefix; end
	if (not ADD_Stamp) then ADD_Stamp=0; end
	if (type(ADD_Stamp)~="number") then return nil; end
	if (not DuckNet_CanTransmit(prefix)) then return false; end					-- Can't transmit now
	DuckNet_ClearOutput(prefix);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"S",ADD_Stamp);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"M",ADD_BlockName);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"A",DUCKNET_ACT_TRANSMIT);
	if (not DuckNet_CodeTable(prefix,ADD_Data,0)) then return nil; end			-- Could not build output with this data
	return DuckNet_DoTransmission(prefix);
end


function DuckNet_SyncTable(prefix,ADD_BlockName,ADD_Stamp)
	if (not ADD_BlockName) then ADD_BlockName=prefix; end
	return DuckNet_Poll(prefix,ADD_Stamp,ADD_BlockName);
end



--[[  Checks if DuckNet is ready to handle your addon  ]]
function DuckNet_Idle(prefix)
	if (not DuckNet_NotCrusial(prefix)) then return false; end;
	if (DuckNet_DB[prefix].Negotiate.HoldOff>DUCKNET_NEG_STOPPED) then return false; end;	-- Negotiation is currently running
	return true;
end


--[[  Checks if DuckNet is ready to transmit  ]]
function DuckNet_CanTransmit(prefix)
	if (not DuckNet_NotCrusial(prefix)) then return false; end;
	if (DuckNet_DB[prefix].Negotiate.HoldOff<DUCKNET_NEG_DONE and DuckNet_DB[prefix].Negotiate.HoldOff>DUCKNET_NEG_STOPPED) then return false; end;		-- Negotiation is off or done
	return true;
end


--[[  Send an entire table directly (Still being designed, so please don't use this yet)  ]]
function DuckNet_SendTable(prefix,t)
	if (not DuckNet_CanTransmit(prefix)) then							-- Invalid or busy
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet is not idle @ SendTable"); end
		return false;
	end;
	-- Insert generic header
	DuckNet_AddKey(prefix,"M","Table");
	DuckNet_AddKey(prefix,"A",DUCKNET_ACT_TRANSMIT);
	DuckNet_NewLine(prefix);

	-- Build transmission queue
	local level=0;												-- Set root
--	table.setn(DuckNet_DB[prefix].Output.Tables,0);				-- Clear
	DuckNet_DB[prefix].Output.Tables={};
	if (not DuckNet_CodeTable(prefix,t,level)) then
	end

	-- Footer
	DuckNet_DoTransmission(prefix);
end


--[[  Poll network for newer data  ]]
function DuckNet_Poll(prefix,stamp,marker,Tversion)
	if (not DuckNet_ClearOutput(prefix)) then
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet is not idle @ Poll"); end
		return false;
	end
	if (not stamp) then stamp=0; end
	DuckNet_AddKey(prefix,"S",stamp);
	if (marker) then DuckNet_AddKey(prefix,"M",marker); end
	if (Tversion) then DuckNet_AddKey(prefix,"T",Tversion); end
	DuckNet_AddKey(prefix,"A",DUCKNET_REQ_NEWDATA);
	DuckNet_StartNegotiation(prefix,marker,stamp);
	if (DuckMod_Present) then DuckMod_DN_Negotiation(nil,marker,stamp,0,true); DuckMod_DN_Neg_SetMyStamp(stamp); end
	return DuckNet_SingleTransmit(prefix);
end


--[[  Clear output buffer  ]]
function DuckNet_ClearOutput(prefix)
	if (not DuckNet_CanTransmit(prefix)) then					-- Invalid or busy
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet is not idle @ ClearOutput"); end
		return false;
	end;
	DuckNet_DB[prefix].Output.Buffer={ };						-- Clear table
	DuckNet_DB[prefix].Output.Line=1;							-- Start at the top
	DuckNet_DB[prefix].Output.Entry=1;							-- Start at the top
	DuckNet_DB[prefix].Output.Tables={ };						-- Clear
	return true;												-- It's cleared
end


--[[  Advance line in output buffer  ]]
function DuckNet_NewLine(prefix)
	if (not DuckNet_CanTransmit(prefix)) then					-- Invalid or busy
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet is not idle @ NewLine"); end
		return false;
	end;
	if (DuckNet_DB[prefix].Output.Buffer[DuckNet_DB[prefix].Output.Line]) then		-- Current line exists
		DuckNet_DB[prefix].Output.Line=DuckNet_DB[prefix].Output.Line+1;		-- Go to next line
	end
	return true;												-- It's cleared
end


--[[  Add a new key/action/marker  ]]
function DuckNet_AddKey(prefix,name,info)
	if (not DuckNet_CanTransmit(prefix)) then					-- Invalid or busy
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet is not idle @ AddKey"); end
		return false;
	end;
	if (string.len(name)~=1) then return false; end;
	local tp=DuckNet_DB[prefix].Output.Buffer;
	if (not tp[DuckNet_DB[prefix].Output.Line]) then
--		tp[DuckNet_DB[prefix].Output.Line]=prefix;
		tp[DuckNet_DB[prefix].Output.Line]="";
	end
	tp[DuckNet_DB[prefix].Output.Line]=tp[DuckNet_DB[prefix].Output.Line]..":"..name..info;
	return true;												-- It's cleared
end


--[[  Add a new table name  ]]
function DuckNet_AddTable(prefix,name)
	return DuckNet_AddEntry(prefix,name);
end


--[[  Add a new entry with data  ]]
function DuckNet_AddEntry(prefix,name,data,rebuild,nosplit)
	if (not DuckNet_CanTransmit(prefix)) then					-- Invalid or busy
		if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: DuckNet is not idle @ AddEntry"); end
		return false;
	end
	if (string.len(name)<1) then return false; end;				-- Name too short
	local tp=DuckNet_DB[prefix].Output.Buffer;
	if (not tp[DuckNet_DB[prefix].Output.Line]) then
--		tp[DuckNet_DB[prefix].Output.Line]=prefix..":E"..DuckNet_DB[prefix].Output.Entry;
		tp[DuckNet_DB[prefix].Output.Line]=":E"..DuckNet_DB[prefix].Output.Entry;
		DuckNet_DB[prefix].Output.Entry=DuckNet_DB[prefix].Output.Entry+1;
	end
	
	-- Add it
	local lastline=tp[DuckNet_DB[prefix].Output.Line];
	local entry=DuckNet_MakeEntry("#",name,data);
	tp[DuckNet_DB[prefix].Output.Line]=tp[DuckNet_DB[prefix].Output.Line]..entry;

	-- Check length. Break if need be
	local max=250-string.len(prefix);
	if (string.len(tp[DuckNet_DB[prefix].Output.Line])>max) then					-- Too long
		if (rebuild) then return false; end											-- Currently rebuilding, data is untransmittable
		if (nosplit) then																	-- Can't split line
			DuckNet_AutoTableLinebreak(prefix,true);										-- Break and rebuild and start next line either way
			tp[DuckNet_DB[prefix].Output.Line]=tp[DuckNet_DB[prefix].Output.Line]..entry;
			if (string.len(tp[DuckNet_DB[prefix].Output.Line])>max) then return false; end	-- Still too long - Can't rebuild
		else
			local overhead=string.len(DUCKNET_ENTRYBREAKCONTINUE);							-- Splitter overhead
			while (string.len(tp[DuckNet_DB[prefix].Output.Line])>max) do					-- Still too long
				if (not DuckNet_NewLine(prefix)) then return false; end						-- Can't add line
				tp[DuckNet_DB[prefix].Output.Line]=tp[DuckNet_DB[prefix].Output.Line-1];	-- Copy before clip
				tp[DuckNet_DB[prefix].Output.Line-1]=string.sub(tp[DuckNet_DB[prefix].Output.Line-1],1,max-overhead)..DUCKNET_ENTRYBREAKCONTINUE;
				tp[DuckNet_DB[prefix].Output.Line]=string.sub(tp[DuckNet_DB[prefix].Output.Line],(max-overhead)+1);
			end
		end
	end
	if (rebuild) then return true; end;									-- Rebuild complete
	-- At this point, any requested data has been added successfully

	-- Table book-keeping
	if (not data) then table.insert(DuckNet_DB[prefix].Output.Tables,name); end	-- Add this table

	return true;												-- It's cleared
end


--[[  Finalise and start tarnsmission  ]]
function DuckNet_DoTransmission(prefix,freeform)
	if (not freeform) then
		if (not DuckNet_NewLine(prefix)) then
			if (DuckNet_Debug) then DuckNet_Chat(prefix.." DEBUG: Could not add line @ DoTransmission"); end
			return false;
		end
		if (not DuckNet_AddKey(prefix,"A",DUCKNET_ACT_TRANSMITDONE)) then return false; end;
	end

	-- Validate buffer
	local tp=DuckNet_DB[prefix].Output.Buffer;
	local overhead=string.len(prefix)+1;
	local seq=1;
	local linebreaks=0;
	local longlines=0;
	while(tp[seq]) do
		if (string.find(tp[seq],"\n")) then linebreaks=linebreaks+1; DuckNet_Chat("ERROR: Linebreak in line "..seq,1,0,0); end
		local total=string.len(tp[seq])+overhead;
		if (total>253) then longlines=longlines+1; DuckNet_Chat("ERROR: Line "..seq.." length: "..total,1,0,0); end
		seq=seq+1;
	end
	if (linebreaks>0 or longlines>0) then
		DuckNet_ClearOutput(prefix);
		DuckNet_Chat("ERROR: Transmission aborted",1,0,0);
		return true;
	end

	if (DuckNet_DB[prefix].PB) then
		DuckNet_DB[prefix].PB:SetMinMaxValues(0,DuckNet_DB[prefix].Output.Line);
		DuckNet_DB[prefix].PB:SetValue(0);
	end

	DuckNet_DB[prefix].Transmitting=DUCKNET_TRANS_START;		-- Start transmisison
	DuckNet_DB[prefix].LastOutput=nil;							-- It's there now
	return true;
end


--[[  Send the first line only  ]]
function DuckNet_SingleTransmit(prefix)
	if (not DuckNet_DB[prefix].Output.Buffer[1]) then return false; end;			-- The line is empty
	DuckNet_Out(prefix,DuckNet_DB[prefix].Output.Buffer[1]);	-- Send it
	return true;
end
